import socket
import select
import cPickle
from cStringIO import StringIO
import os
from SocketServer import TCPServer, StreamRequestHandler, ThreadingMixIn

__all__ = ['sendreq', 'recvresp', 'RPCServer', 'RPCClient', 'ThreadedRPCServer']

class _RPCRequestHandler(StreamRequestHandler):
    '''\
    The handler class used by RPCServer by default.
    This class is not useful on its own.
    '''
    def handle(self):        
        msg = recvresp(self.connection)
        obj = self.server.obj
        method = msg['method']
        args = msg['args']
        del msg['method']        
        if not hasattr(obj, method):
            print "unknown method ", method
            "break spectacularly so that no one calls wrong method names"
            raise "unknown method, %s" % method
        m = getattr(obj, method)
        msg = m(*args)
        if msg is not None:
            sendreq(self.connection, msg)            

class RPCServer(TCPServer):
    '''\
    This class enables the wrapped objects' methods to be called remotely. The 
    remote clients must create an RPCClient to talk to RPCServer objects. 
    Only the methods can be called remotely, class members can not be accessed.
    
    Example:
     
    On server:
    
    >>> class Dog:
    >>>      def bark(self):
    >>>          return "WOOF!"
    >>> dog = Dog()
    >>> dog.rpc_host = ''; dog.rpc_port = 8000 #the objects passed to rpc server must have these members
    >>> rpc_server_for_dog = RPCServer(dog)
    >>> rpc_server_for_dog.serve_forever()
    
    On Client:
    
    >>> dog = RPCClient('localhost', 8000)
    >>> print dog.bark()
    WOOF!    
    '''
    def __init__(self, obj, request_handler=_RPCRequestHandler):
        self.obj = obj
        TCPServer.__init__(self, (self.obj.rpc_host, self.obj.rpc_port), request_handler)        
        print "listening for rpc requests on port ", self.obj.rpc_port
    def handle_request_nb(self):
        """handle the requests in a nonblocking way"""
        rl,wl,xl = select.select([self.socket],[], [], 0)
        if len(rl) > 0:
            self.handle_request()
class ThreadedRPCServer(ThreadingMixIn, RPCServer): pass 
class _RPCMethod:
    def __init__(self, method, client):
        self.method = method
        self.client = client
    def __call__(self, *args):
        msg = {'method': self.method, 'args':args}
        sock = socket.socket()        
        try:
            sock.connect((self.client.host, self.client.port))               
            sendreq(sock, msg)
            sock.shutdown(1)                
            resp = recvresp(sock) 
            sock.close()               
            return resp
        except socket.error, msg:
            print "can't make an rpc call ",msg
            
class RPCClient:
    '''\
    Turns method calls into network messages and returns the output 
    returned by the remote object. See the documentation for RPCServer 
    for an example.
    '''
    def __init__(self, host, port):
        self.host = host
        self.port = port
    
    def __getattr__(self, method):
        if method in ['__call__', 'mro', 'has_key', '__contains__']:
            raise AttributeError()        
        return _RPCMethod(method, self)
        
    
         
            
def sendreq(sock, msg):    
    """\
    serializes the object and sends it as a message
    Example:
    
    Alice:
    
    >>> sendreq(bob, ['this' : 'is an array, not string']
    
    Bob:
    
    >>> arr_from_alice = recresp(alice) 
    """    
    try:        
        sock.sendall(cPickle.dumps(msg))            
    except socket.erorr, err_msg:
        sock.close()
        print "unable to send request, exception in socket, error = ",err_msg    

def recvresp(sock):
    """\
    receive the message and deserialize. See sendreq for an example.
    """    
    try:
        s = StringIO()
        while True:            
            buff = sock.recv(1024)
            if not buff:
                break
            s.write(buff)                      
    except socket.error, err_msg:
        print "unable to receive response, exception in socket, error =", err_msg
        sock.close()
        return None
    try:
        return cPickle.loads(s.getvalue())
    except cPickle.PickleError, err_msg:
        sock.close()
        print "unable to unserialize, error =", err_msg 
        